import 'dart:async';

import 'package:flutter/foundation.dart';

enum LOGLevel {
  debug(LOGColors.cyan, 0),
  info(LOGColors.brightGreen, 1),
  warning(LOGColors.brightYellow, 2),
  error(LOGColors.brightRed, 3),
  ;

  final LOGColors color;
  final int level;
  const LOGLevel(this.color, this.level);
}

enum LOGColors {
  black('\x1B[30m'),
  red('\x1B[31m'),
  green('\x1B[32m'),
  yellow('\x1B[33m'),
  blue('\x1B[34m'),
  magenta('\x1B[35m'),
  cyan('\x1B[36m'),
  white('\x1B[37m'),
  brightBlack('\x1B[90m'),
  brightRed('\x1B[91m'),
  brightGreen('\x1B[92m'),
  brightYellow('\x1B[93m'),
  brightBlue('\x1B[94m'),
  brightMagenta('\x1B[95m'),
  brightCyan('\x1B[96m'),
  brightWhite('\x1B[97m'),
  reset('\x1B[0m'),
  ;

  final String colorStr;
  const LOGColors(this.colorStr);
}

LOGLevel defaultLevel = kDebugMode ? LOGLevel.debug : LOGLevel.info;

class _BlankLog {
  _BlankLog operator <<(dynamic message) => this;
  _BlankLog call(dynamic message) => this;
}

class LOG extends _BlankLog {
  final bool printAnyway;
  final LOGLevel level;
  final LOGLevel _printlevel = defaultLevel;
  final LOGColors? color;

  int _printLine = 1;
  static bool globalFullPath = true;
  static bool globalPrintFlag = true;

  int i = 0;
  String prefix = '';
  List<String> _list = [];

  get _shouldPrint =>
      printAnyway || (globalPrintFlag && _printlevel.level <= level.level);

  LOG({
    this.printAnyway = false,
    this.level = LOGLevel.info,
    this.color,
    String? text,
    bool? fullPath,
    bool newLine = false,
    int printLineIndex = 1,
  }) {
    if (_shouldPrint) {
      _printLine = printLineIndex;
      fullPath ??= LOG.globalFullPath;
      final stackTrace = StackTrace.current;
      final fileLine = stackTrace.toString().split('\n')[_printLine];
      final regexp =
          RegExp(r'^#\d+\s+(.+) \((.+\/)(.+:\d+:?\d+?)\)$', multiLine: true);
      RegExpMatch result = regexp.firstMatch(fileLine)!;
      String funName = result.group(1)!;
      String funNamePrefix = result.group(2)!;
      String fileInfo = result.group(3)!;
      prefix =
          'Flutter log [${fullPath ? funNamePrefix : ''}$fileInfo] [$funName]:${newLine ? '\n     ' : ''}   ';
      _add(prefix);
      if (text != null) {
        _add(text);
      }
    }
  }

  void _add(dynamic obj) {
    String str = obj.toString();
    _list.add(str);
    i = _list.length;
    final count = i;
    Future.delayed(Duration.zero, () {
      if (count == i) _log();
    });
  }

  void _log() {
    String color = this.color?.colorStr ?? level.color.colorStr;
    _list = _list.map(
      (e) {
        var str = e;
        if (e.contains('\n')) str = e.replaceAll('\n', '\n        ');
        return str;
      },
    ).toList();
    print("$color${_list.join(' ')}${LOGColors.reset.colorStr}");
    _list.clear();
  }

  @override
  _BlankLog operator <<(dynamic message) {
    if (_shouldPrint) {
      _add(message);
      return this;
    } else {
      return _BlankLog();
    }
  }

  @override
  _BlankLog call(dynamic message) {
    if (_shouldPrint) {
      _add(message);
      return this;
    } else {
      return _BlankLog();
    }
  }

  static LOG get debug => LOG(level: LOGLevel.debug, printLineIndex: 2);

  static LOG get warning => LOG(level: LOGLevel.warning, printLineIndex: 2);

  static LOG get info => LOG(level: LOGLevel.info, printLineIndex: 2);

  static LOG get error => LOG(level: LOGLevel.error, printLineIndex: 2);

  static void forbiddenPrint() {
    globalPrintFlag = false;
  }

  static void allowPrint() {
    globalPrintFlag = true;
  }

  static void setDefaultLevel(LOGLevel level) {
    defaultLevel = level;
  }
}
